import type {
  BookingResult,
  CalendarInfo,
  ErrorInfo,
  FormFields,
  IPageParser,
  PageType,
  ParseResult,
} from '@shared/types/'
import type { TimeslotInfo } from '@shared/types/automation'
import { parseAppointmentInfo as parseInfoShared } from '@shared/parser/tdabs'
import * as cheerio from 'cheerio'
import { createContextLogger, generateTraceId, log, PerformanceLogger } from '../logger/index'
import { LogCategory } from '../logger/types'

/** 页面解析器 */
export class PageParser implements IPageParser {
  /** 日志记录器 */
  private logger = createContextLogger({ component: 'PageParser', category: LogCategory.BROWSER })
  /** 操作ID */
  private operationId: string = ''

  /**
   * 识别页面类型
   *
   * @description 识别页面类型，基于 URL 和页面内容判断
   *
   * @param {string} html - 页面HTML内容
   * @param {string} url - 页面URL
   * @returns {PageType} 页面类型
   */
  identifyPageType(html: string, url: string): PageType {
    this.operationId = generateTraceId()
    PerformanceLogger.start(`identify-page-${this.operationId}`)

    this.logger.debug('开始识别页面类型', {
      operationId: this.operationId,
      url,
      htmlSize: `${(html.length / 1024).toFixed(2)}KB`,
    })

    const $ = cheerio.load(html)

    // 基于 URL 判断
    if (url.includes('cmd=cmdShowHome')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'home',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'home'
    }
    if (url.includes('cmd=cmdStartNewApplication')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'terms',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'terms'
    }
    if (url.includes('cmd=cmdInputPersonalInfo')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'personal_info',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'personal_info'
    }
    if (url.includes('cmd=cmdCheckIdentifierAction')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'identifier_check',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'identifier_check'
    }
    if (url.includes('cmd=cmdGetCalendarAction')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'calendar',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'calendar'
    }
    if (url.includes('cmd=cmdGetTimeslotListAction')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'timeslot',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'timeslot'
    }
    if (url.includes('cmd=cmdConfirmAppointment')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'confirmation',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'confirmation'
    }
    if (url.includes('cmd=cmdSubmitAppointmentAction')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'result',
        method: 'URL检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'result'
    }

    // 基于页面内容判断
    const title = $('title').text()
    const bodyText = ($('body').text() || '').toLowerCase()

    if (title.includes('系统维护') || bodyText.includes('系统维护') || title.includes('系統維護') || bodyText.includes('系統維護')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'maintenance',
        method: '内容检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'maintenance'
    }
    if (title.includes('错误') || bodyText.includes('错误') || title.includes('錯誤') || bodyText.includes('錯誤') || $('td[bgcolor="red"]').length > 0) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'error',
        method: '内容检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'error'
    }
    if ($('input[name="storedAppointmentCalendarYear"]').length > 0) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'calendar',
        method: '内容检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'calendar'
    }
    if ($('a[onclick*="onClickTimeslot"]').length > 0) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'timeslot',
        method: '内容检测',
        duration,
        durationText: `${duration}ms`,
      })
      return 'timeslot'
    }
    if (bodyText.includes('參考編號') || bodyText.includes('参考编号')) {
      const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
      this.logger.debug('页面类型识别完成', {
        operationId: this.operationId,
        pageType: 'result',
        duration,
        durationText: `${duration}ms`,
      })
      return 'result'
    }

    const duration = PerformanceLogger.end(`identify-page-${this.operationId}`)
    this.logger.warn('无法识别页面类型', {
      operationId: this.operationId,
      url,
      pageType: 'unknown',
      duration,
      durationText: `${duration}ms`,
    })
    return 'unknown'
  }

  /**
   * 提取页面 Screen ID（如 SC-512-1-002），用于更精确的页面就绪判定。
   */
  extractScreenId(html: string): ParseResult<string> {
    try {
      const $ = cheerio.load(html)
      const title = $('title').text() || ''
      const body = $('body').text() || ''
      const matchFromTitle = title.match(/SC-\d{3}-\d-\d{3}/i)
      if (matchFromTitle)
        return { success: true, data: matchFromTitle[0] }
      const matchFromBody = body.match(/SC-\d{3}-\d-\d{3}/i)
      if (matchFromBody)
        return { success: true, data: matchFromBody[0] }
      return { success: false, error: '未找到 Screen ID' }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.logger.error('提取 Screen ID 失败', { error: message })
      return { success: false, error: message }
    }
  }

  /**
   * 提取表单字段
   *
   * @description 提取页面中的表单字段
   *
   * @param {string} html - 页面HTML内容
   * @param {string} formName - 表单名称
   * @returns {ParseResult<FormFields>} 表单字段
   */
  extractFormFields(html: string, formName?: string): ParseResult<FormFields> {
    const operationId = generateTraceId()
    PerformanceLogger.start(`extract-form-${operationId}`)

    this.logger.debug('开始提取表单字段', {
      operationId,
      formName: formName || 'default',
      htmlSize: `${(html.length / 1024).toFixed(2)}KB`,
    })

    try {
      const $ = cheerio.load(html)
      const fields: FormFields = {}

      const formSelector = formName ? `form[name="${formName}"]` : 'form'
      const $form = $(formSelector).first()
      if ($form.length === 0) {
        const duration = PerformanceLogger.end(`extract-form-${operationId}`)
        this.logger.warn('未找到表单', {
          operationId,
          formName,
          formSelector,
          duration,
          durationText: `${duration}ms`,
        })
        return { success: false, error: '未找到表单' }
      }

      $form.find('input, select, textarea').each((_, element) => {
        const $el = $(element)
        const name = $el.attr('name')
        const value = ($el.attr('value') || ($el.val() as string)) ?? ''
        if (name)
          fields[name] = value
      })

      $('input[type="hidden"]').each((_, element) => {
        const $el = $(element)
        const name = $el.attr('name')
        const value = $el.attr('value')
        if (name && value)
          fields[name] = value
      })

      const duration = PerformanceLogger.end(`extract-form-${operationId}`)
      const fieldCount = Object.keys(fields).length

      this.logger.debug('表单字段提取成功', {
        operationId,
        formName,
        fieldCount,
        duration,
        durationText: `${duration}ms`,
      })

      return { success: true, data: fields }
    }
    catch (e: unknown) {
      const duration = PerformanceLogger.measure(`extract-form-${operationId}`) || 0
      const message = e instanceof Error ? e.message : String(e)

      if (e instanceof Error) {
        log.system.error(e, 'PageParser.extractFormFields', {
          operationId,
          formName,
          user_action: '系统尝试提取表单字段',
          recovery_suggestion: '检查页面HTML结构',
        })
      }
      else {
        this.logger.error('提取表单字段失败', {
          operationId,
          formName,
          error: message,
          duration,
          durationText: `${duration}ms`,
        })
      }
      return { success: false, error: message }
    }
  }

  /**
   * 解析可用日期
   *
   * @description 解析页面中的可用日期信息
   *
   * @param {string} html - 页面HTML内容
   * @returns {ParseResult<string[]>} 可用日期信息
   */
  parseAvailableDates(html: string): ParseResult<string[]> {
    try {
      const $ = cheerio.load(html)
      const availableDates: string[] = []

      $('a[onclick*="onClickDate"]').each((_, element) => {
        const $el = $(element)
        const onclickAttr = $el.attr('onclick')
        if (!onclickAttr)
          return
        const match = onclickAttr.match(/onClickDate\('([^']+)'\)/)
        if (match) {
          const dateStr = match[1] // DD-MM-YYYY
          if (this.isValidDate(dateStr))
            availableDates.push(dateStr)
        }
      })

      const uniqueDates = [...new Set(availableDates)].sort((a, b) => {
        const dateA = this.parseDateString(a)
        const dateB = this.parseDateString(b)
        return dateA.getTime() - dateB.getTime()
      })

      return { success: true, data: uniqueDates }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)

      if (e instanceof Error) {
        log.system.error(e, 'PageParser.parseAvailableDates', {
          user_action: '系统尝试解析可用预约日期',
          recovery_suggestion: '检查页面HTML结构和日期元素',
        })
      }
      else {
        this.logger.error('解析可用日期失败', { error: message })
      }
      return { success: false, error: message }
    }
  }

  /**
   * 解析“月份统计”：年月、总天数、可预约、休息日、约满、暂不可约
   * 返回说明：
   * - year: 年
   * - monthIndex0: 月索引（0-11）
   * - daysInMonth: 总天数
   * - availableCount: 可预约天数
   * - restCount: 休息日天数
   * - bookedCount: 约满天数
   * - tempCount: 暂不可约天数
   */
  parseCalendarSummary(html: string): ParseResult<{ year: number, monthIndex0: number, daysInMonth: number, availableCount: number, restCount: number, bookedCount: number, tempCount: number }> {
    try {
      const $ = cheerio.load(html)

      // 首先检查是否是真正的日历页面
      // 真正的日历页面应该有以下特征之一：
      // 1. 有日历表头 `.calendar-table-header-cell`
      // 2. 有日历相关的隐藏字段 `storedAppointmentCalendarYear` 和 `storedAppointmentCalendarMonth`
      // 3. 有可用日期链接 `a[onclick*="onClickDate"]`
      // 4. 有日历表格相关结构
      const hasCalendarHeader = $('.calendar-table-header-cell').length > 0
      const hasCalendarYearField = $('input[name="storedAppointmentCalendarYear"]').length > 0
      const hasCalendarMonthField = $('input[name="storedAppointmentCalendarMonth"]').length > 0
      const hasDateLinks = $('a[onclick*="onClickDate"]').length > 0
      const hasCalendarTable = $('.calendar-table, .calendar-inner-table').length > 0

      // 如果没有任何日历相关元素，说明这不是日历页面（可能是错误页面）
      if (!hasCalendarHeader && !(hasCalendarYearField && hasCalendarMonthField) && !hasDateLinks && !hasCalendarTable)
        return { success: false, error: 'not a calendar page' }

      // 解析年月
      const detectYearMonth = (): { year: number, monthIndex0: number } | null => {
        const yField = $('input[name="storedAppointmentCalendarYear"]').val() as string | undefined
        const mField = $('input[name="storedAppointmentCalendarMonth"]').val() as string | undefined
        if (yField && mField) {
          const y = Number.parseInt(String(yField), 10)
          const m = Number.parseInt(String(mField), 10)
          if (!Number.isNaN(y) && !Number.isNaN(m))
            return { year: y, monthIndex0: m }
        }
        // 从表头中提取年月
        const headerEl = $('.calendar-table-header-cell').first()
        const headerText = headerEl.text() || ''
        const mh = headerText.match(/(\d{4})年\s*(\d{1,2})月/)
        if (mh) {
          const y = Number.parseInt(mh[1], 10)
          const m = Number.parseInt(mh[2], 10) - 1
          if (!Number.isNaN(y) && !Number.isNaN(m))
            return { year: y, monthIndex0: m }
        }
        // 从可用日期链接中提取年月
        const link = $('a[onclick*="onClickDate"]').first()
        if (link.length > 0) {
          const onclick = link.attr('onclick') || ''
          const m = onclick.match(/onClickDate\('([\d-]+)'\)/)
          if (m) {
            const parts = m[1].split('-')
            if (parts.length === 3) {
              const y = Number.parseInt(parts[2], 10)
              const m0 = Number.parseInt(parts[1], 10) - 1
              if (!Number.isNaN(y) && !Number.isNaN(m0))
                return { year: y, monthIndex0: m0 }
            }
          }
        }
        // 无法解析年月，返回 null
        return null
      }

      // 解析年月，如果无法解析，返回错误
      const ym = detectYearMonth()
      if (!ym)
        return { success: false, error: 'year/month not found' }

      const year = ym.year
      const monthIndex0 = ym.monthIndex0
      const daysInMonth = new Date(year, monthIndex0 + 1, 0).getDate()

      // 解析可用日期
      const availableDayNumsSet = new Set<number>()
      $('a[onclick*="onClickDate"]').each((_, el) => {
        const onclick = $(el).attr('onclick') || ''
        const m = onclick.match(/onClickDate\('([^']+)'\)/)
        if (m) {
          const dm = m[1].match(/^(\d{1,2})-/)
          if (dm) {
            const d = Number.parseInt(dm[1], 10)
            if (!Number.isNaN(d) && d >= 1 && d <= daysInMonth)
              availableDayNumsSet.add(d)
          }
        }
      })

      // 解析休息日,休息日是周六和周日
      const restDayNumsSet = new Set<number>()
      for (let d = 1; d <= daysInMonth; d++) {
        const dow = new Date(year, monthIndex0, d).getDay()
        if (dow === 0 || dow === 6)
          restDayNumsSet.add(d)
      }

      // 解析约满日（灰色文字或灰色背景）
      const fullyBookedDayNumsSet = new Set<number>()
      // 检查灰色文字的日期
      $('span[style*="color:#999"], span[style*="color: #999"], span[style*="color:#999999"], span[style*="color: #999999"]').each((_, el) => {
        const text = ($(el).text() || '').trim()
        const n = Number.parseInt(text, 10)
        if (!Number.isNaN(n) && n >= 1 && n <= daysInMonth)
          fullyBookedDayNumsSet.add(n)
      })
      // 检查灰色背景的日期
      $('tr[style*="background-color:#CCCCCC"], tr[style*="background-color: #CCCCCC"]').find('td').each((_, el) => {
        const text = ($(el).text() || '').trim()
        const n = Number.parseInt(text, 10)
        if (!Number.isNaN(n) && n >= 1 && n <= daysInMonth)
          fullyBookedDayNumsSet.add(n)
      })

      // 解析所有出现在日历表格中的日期数字（用于判断"暂不可约"）
      // 优先从日历表格单元格中解析，避免解析到页面其他位置的数字
      const presentDayNumsSet = new Set<number>()
      // 检查日历表格单元格中的日期
      $('.calendar-table td, .calendar-inner-table td, table.calendar-table td, table.calendar-inner-table td').each((_, el) => {
        const text = ($(el).text() || '').trim()
        const n = Number.parseInt(text, 10)
        if (!Number.isNaN(n) && n >= 1 && n <= daysInMonth)
          presentDayNumsSet.add(n)
      })
      // 如果日历表格中没有日期数字，则从日历表格容器中解析
      if (presentDayNumsSet.size === 0) {
        const calendarContainer = $('.calendar-table, .calendar-inner-table, table[class*="calendar"]').first()
        if (calendarContainer.length > 0) {
          calendarContainer.find('td').each((_, el) => {
            const text = ($(el).text() || '').trim()
            const n = Number.parseInt(text, 10)
            if (!Number.isNaN(n) && n >= 1 && n <= daysInMonth)
              presentDayNumsSet.add(n)
          })
        }
        // 如果日历表格容器中没有日期数字，则从可用日期链接中解析
        if (presentDayNumsSet.size === 0) {
          $('a[onclick*="onClickDate"]').each((_, el) => {
            const parent = $(el).closest('td, tr, table')
            parent.find('td').each((__, td) => {
              const text = ($(td).text() || '').trim()
              const n = Number.parseInt(text, 10)
              if (!Number.isNaN(n) && n >= 1 && n <= daysInMonth)
                presentDayNumsSet.add(n)
            })
          })
        }
      }

      // 计算暂不可约天数
      let tempUnavailable = 0
      for (let d = 1; d <= daysInMonth; d++) {
        if (!presentDayNumsSet.has(d))
          continue
        if (restDayNumsSet.has(d))
          continue
        if (availableDayNumsSet.has(d))
          continue
        if (fullyBookedDayNumsSet.has(d))
          continue
        tempUnavailable++
      }

      // 计算可预约天数、休息日天数、约满天数、暂不可约天数
      const availableCount = Array.from(availableDayNumsSet).filter(d => presentDayNumsSet.has(d)).length
      const restCount = Array.from(restDayNumsSet).filter(d => presentDayNumsSet.has(d)).length
      const bookedCount = Array.from(fullyBookedDayNumsSet).filter(d => presentDayNumsSet.has(d)).length
      const tempCount = tempUnavailable

      // 返回结果
      return { success: true, data: { year, monthIndex0, daysInMonth, availableCount, restCount, bookedCount, tempCount } }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)
      this.logger.warn('parseCalendarSummary 解析失败', { error: message })
      return { success: false, error: message }
    }
  }

  /**
   * 解析可用时段
   *
   * @description 解析页面中的可用时段信息
   *
   * @param {string} html - 页面HTML内容
   * @returns {ParseResult<TimeslotInfo[]>} 可用时段信息
   */
  parseAvailableTimeslots(html: string): ParseResult<TimeslotInfo[]> {
    try {
      const $ = cheerio.load(html)
      const timeslots: TimeslotInfo[] = []

      $('a.redlink[onclick*="onClickTimeslot"]').each((_, element) => {
        const $el = $(element)
        const onclickAttr = $el.attr('onclick')
        const displayTime = $el.text().trim()
        if (!onclickAttr)
          return
        const match = onclickAttr.match(/onClickTimeslot\('([^']+)',\s*'([^']+)',\s*'([^']+)',\s*'([^']+)'\)/)
        if (match) {
          const [, timeslotId, dateDDMMYYYY, startTimeHHMM, endTimeHHMM] = match
          timeslots.push({
            id: timeslotId,
            date: dateDDMMYYYY,
            start_time: startTimeHHMM,
            end_time: endTimeHHMM,
            display_time: displayTime,
          })
        }
      })

      timeslots.sort((a, b) => a.start_time.localeCompare(b.start_time))
      return { success: true, data: timeslots }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)

      if (e instanceof Error) {
        log.system.error(e, 'PageParser.parseAvailableTimeslots', {
          user_action: '系统尝试解析可用预约时段',
          recovery_suggestion: '检查页面HTML结构和时段元素',
        })
      }
      else {
        this.logger.error('解析可用时段失败', { error: message })
      }
      return { success: false, error: message }
    }
  }

  /**
   * 解析预约结果
   *
   * @description 解析预约结果页面，返回预约结果信息
   *
   * @param {string} html - 页面HTML内容
   * @returns {ParseResult<BookingResult>} 预约结果
   */
  parseBookingResult(html: string): ParseResult<BookingResult> {
    try {
      const $ = cheerio.load(html)
      const bodyText = $('body').text()

      // 兼容含空格/连字符的参考编号格式，并清理空白字符
      const referenceMatch = bodyText.match(/(參考編號|参考编号)[：:\s]*([A-Z0-9\s-]+)/i)
      if (referenceMatch) {
        const cleanedRef = (referenceMatch[2] || '').replace(/\s+/g, '')
        return {
          success: true,
          data: {
            success: true,
            reference_number: cleanedRef,
            message: '预约成功',
          },
        }
      }

      const errorInfo = this.detectError(html)
      if (errorInfo.success && errorInfo.data) {
        return {
          success: true,
          data: {
            success: false,
            error_code: errorInfo.data.code,
            message: errorInfo.data.message,
          },
        }
      }

      return { success: false, error: '无法解析预约结果' }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)

      if (e instanceof Error) {
        log.system.error(e, 'PageParser.parseBookingResult', {
          user_action: '系统尝试解析预约结果',
          recovery_suggestion: '检查页面HTML结构和结果元素',
        })
      }
      else {
        this.logger.error('解析预约结果失败', { error: message })
      }
      return { success: false, error: message }
    }
  }

  /**
   * 解析查询预约结果页面（cmdGetApptInfoByRefNo）
   * 返回结构化的预约信息（若能解析）。
   */
  parseAppointmentInfo(html: string): ParseResult<any> {
    const res = parseInfoShared(html)
    return res.success ? { success: true, data: res.data } : { success: false, error: res.error || 'parse failed' }
  }

  /**
   * 解析日历信息
   *
   * @description 解析页面中的日历信息
   *
   * @param {string} html - 页面HTML内容
   * @returns {ParseResult<CalendarInfo>} 日历信息
   */
  parseCalendarInfo(html: string): ParseResult<CalendarInfo> {
    try {
      const $ = cheerio.load(html)
      const year = $('input[name="storedAppointmentCalendarYear"]').val() as string
      const month = $('input[name="storedAppointmentCalendarMonth"]').val() as string
      if (!year || !month) {
        return { success: false, error: '未找到日历信息' }
      }

      const datesResult = this.parseAvailableDates(html)
      const availableDates = datesResult.success ? datesResult.data! : []

      return {
        success: true,
        data: { year, month, available_dates: availableDates },
      }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)

      if (e instanceof Error) {
        log.system.error(e, 'PageParser.parseCalendarInfo', {
          user_action: '系统尝试解析日历信息',
          recovery_suggestion: '检查页面HTML结构和日历元素',
        })
      }
      else {
        this.logger.error('解析日历信息失败', { error: message })
      }
      return { success: false, error: message }
    }
  }

  /**
   * 检测页面错误
   *
   * @description 检测页面中的错误信息
   *
   * @param {string} html - 页面HTML内容
   * @returns {ParseResult<ErrorInfo>} 错误信息
   */
  detectError(html: string): ParseResult<ErrorInfo> {
    try {
      const $ = cheerio.load(html)
      const bodyTextRaw = $('body').text()
      const bodyText = (bodyTextRaw || '').replace(/\s+/g, '').toLowerCase()
      // 提取页面显式错误文案（若有）
      const errorBlockTextRaw = (
        $('#errTable #errMsg').first().text()
        || $('#errTable .errormsg').first().text()
        || $('.errorlayer .errormsg').first().text()
        || ''
      )
      const errorBlockText = (errorBlockTextRaw || '').replace(/\s+/g, '')
      // 浏览器内置 NetError（如连接已重置/ERR_CONNECTION_RESET）
      if (bodyText.includes('连接已重置') || bodyText.includes('err_connection_reset') || bodyText.includes('无法访问此网站')) {
        return { success: true, data: { type: 'network_error', code: 'CONNECTION_RESET', message: '连接已重置' } }
      }
      // 访问过于频繁/速率限制
      if (
        bodyText.includes('频繁')
        || bodyText.includes('過於頻繁')
        || bodyText.includes('too frequent')
        || bodyText.includes('too many requests')
        || bodyText.includes('too many request')
        || bodyText.includes('429')
      ) {
        return { success: true, data: { type: 'frequent_access', code: 'FREQUENT_ACCESS', message: '访问过于频繁，请稍后再试' } }
      }

      // 已登记预约（例如错误码 [512-E-0001]）
      const errorCodeRaw = $('.errorCode').first().text() || ''
      const errorCodeNorm = (errorCodeRaw || '').replace(/\s+|\[|\]/g, '').toUpperCase()
      const alreadyBookedByCode = errorCodeNorm.includes('512-E-0001')
      const alreadyBookedByText = (
        errorBlockText.includes('你早前巳成功登記同一服務類型的預約')
        || errorBlockText.includes('你早前已成功登记同一服务类型的预约')
        || bodyText.includes('512-e-0001')
      )
      if (alreadyBookedByCode || alreadyBookedByText) {
        return { success: true, data: { type: 'unknown_error', code: 'ALREADY_BOOKED', message: '已存在同服务类型的预约（512-E-0001）' } }
      }
      // 時段經已額滿（例如错误码 [512-E-0003]）
      // 注意：错误信息可能在 .step div 中，不仅限于标准错误容器
      const stepErrorText = $('.step').text() || ''
      const stepErrorTextNorm = (stepErrorText || '').replace(/\s+/g, '').toLowerCase()
      const timeslotFullByCode = errorCodeNorm.includes('512-E-0003') || stepErrorTextNorm.includes('512-e-0003')
      const timeslotFullByText = (
        errorBlockText.includes('時段經已額滿')
        || errorBlockText.includes('时段经已额满')
        || bodyText.includes('512-e-0003')
        || stepErrorTextNorm.includes('時段經已額滿')
        || stepErrorTextNorm.includes('时段经已额满')
        || bodyText.includes('時段經已額滿')
        || bodyText.includes('时段经已额满')
      )
      if (timeslotFullByCode || timeslotFullByText) {
        // 提取完整的错误消息（包含错误码）
        // 使用分步匹配避免正则回溯问题：先匹配主要文本，再提取错误码
        let errorMessage = '時段經已額滿，請重新選擇時段（512-E-0003）'
        const baseText = stepErrorText || bodyTextRaw
        const baseMatch = baseText.match(/時段經已額滿[^。]*。?/)
        if (baseMatch) {
          const codeMatch = baseText.match(/\[(\d+-E-\d+)\]/)
          errorMessage = baseMatch[0].trim() + (codeMatch ? ` [${codeMatch[1]}]` : '')
        }
        return { success: true, data: { type: 'unknown_error', code: 'TIMESLOT_FULL', message: errorMessage } }
      }

      // 重复登录/系统中断（例如错误码 [512-E-0044]）
      // 错误信息可能在 .step div 中
      const duplicateLoginByCode = errorCodeNorm.includes('512-E-0044') || stepErrorTextNorm.includes('512-e-0044') || bodyText.includes('512-e-0044')
      const duplicateLoginByText = (
        errorBlockText.includes('重複登入')
        || errorBlockText.includes('重覆登入')
        || errorBlockText.includes('重复登入')
        || errorBlockText.includes('重複登入系統')
        || errorBlockText.includes('重覆登入系統')
        || errorBlockText.includes('重复登入系统')
        || errorBlockText.includes('使用者重覆登入')
        || errorBlockText.includes('使用者重複登入')
        || errorBlockText.includes('使用者重复登入')
        || errorBlockText.includes('系統而中斷')
        || errorBlockText.includes('系统而中断')
        || stepErrorTextNorm.includes('重複登入')
        || stepErrorTextNorm.includes('重覆登入')
        || stepErrorTextNorm.includes('重复登入')
        || bodyText.includes('重複登入')
        || bodyText.includes('重覆登入')
        || bodyText.includes('重复登入')
      )
      if (duplicateLoginByCode || duplicateLoginByText) {
        // 提取完整的错误消息（包含错误码）
        let errorMessage = '服務因使用者重覆登入系統而中斷。請避免於短時間內不停刷新頁面並在30分鐘後再次嘗試登入系統。 [512-E-0044]'
        const baseText = stepErrorText || bodyTextRaw
        const baseMatch = baseText.match(/[^。]*重[複覆]登入[^。]*。?/)
        if (baseMatch) {
          const codeMatch = baseText.match(/\[(\d+-E-\d+)\]/)
          errorMessage = baseMatch[0].trim() + (codeMatch ? ` [${codeMatch[1]}]` : ' [512-E-0044]')
        }
        return { success: true, data: { type: 'unknown_error', code: 'DUPLICATE_LOGIN', message: errorMessage } }
      }

      if (bodyText.includes('系统繁忙') || bodyText.includes('系統繁忙') || bodyText.includes('server busy')) {
        return { success: true, data: { type: 'system_busy', code: '512', message: '系统繁忙，请稍后再试' } }
      }
      if (bodyText.includes('会话超时') || bodyText.includes('session timeout')) {
        return { success: true, data: { type: 'session_timeout', code: 'SESSION_TIMEOUT', message: '会话已超时，请重新开始' } }
      }
      if (bodyText.includes('ip') && (bodyText.includes('锁定') || bodyText.includes('blocked'))) {
        return { success: true, data: { type: 'ip_lock', code: 'IP_BLOCKED', message: 'IP 地址被暂时锁定' } }
      }
      if (bodyText.includes('系统维护') || bodyText.includes('maintenance')) {
        return { success: true, data: { type: 'maintenance', code: 'MAINTENANCE', message: '系统正在维护' } }
      }
      // 明确“查询不到记录”场景：
      // 1) 常见关键词/短语（中/英/繁）
      // 2) 错误块中的明确提示
      // 3) 官方错误代码（如 [512-W-0013]）
      // 4) 语义模式：未有 + 參照/参考 + 編號/编号 + 記錄/记录
      const noRecordByPhrase = (
        bodyText.includes('未找到')
        || bodyText.includes('沒有紀錄')
        || bodyText.includes('没有记录')
        || bodyText.includes('no record')
      )
      const noRecordByErrCode = bodyText.includes('512-w-0013') || errorBlockText.includes('512-w-0013')
      const noRecordByPattern = /(未有|未有閣下所填寫的)(.*)?(參照|参考)(.*)?(編號|编号)(.*)?(記錄|记录)/.test(bodyText) || /(未有|未有閣下所填寫的)(.*)?(參照|参考)(.*)?(編號|编号)(.*)?(記錄|记录)/.test(errorBlockText)
      const noRecordByErrorBlock = (
        !!errorBlockText
        && (
          /(未有|未有閣下所填寫的)(.*)?(參照|参考)(.*)?(編號|编号)(.*)?(記錄|记录)/.test(errorBlockText)
          || errorBlockText.includes('請重新填寫')
          || errorBlockText.includes('请重新填写')
        )
      )
      if (noRecordByPhrase || noRecordByErrCode || noRecordByPattern || noRecordByErrorBlock) {
        // 诊断日志：帮助定位为何命中“未找到记录”
        try {
          this.logger.warn('detectError 命中 NO_RECORD', {
            title: $('title').text(),
            error_block_text: errorBlockText?.slice(0, 200),
            by_phrase: noRecordByPhrase,
            by_err_code: noRecordByErrCode,
            by_pattern: noRecordByPattern,
            by_error_block: noRecordByErrorBlock,
          })
        }
        catch {}
        return {
          success: true,
          data: {
            // 维持现有错误类型联合（不新增枚举项），用 code 标识 NO_RECORD
            type: 'unknown_error',
            code: 'NO_RECORD',
            message: errorBlockText || '未找到对应记录',
          },
        }
      }

      const errorElements = $('td[bgcolor="red"], .error, .alert-danger')
      if (errorElements.length > 0) {
        const errorText = errorElements.first().text().trim()
        return { success: true, data: { type: 'unknown_error', code: 'UNKNOWN', message: errorText || '发生未知错误' } }
      }

      return { success: false, error: '未检测到错误' }
    }
    catch (e: unknown) {
      const message = e instanceof Error ? e.message : String(e)

      if (e instanceof Error) {
        log.system.error(e, 'PageParser.detectError', {
          user_action: '系统尝试检测页面错误',
          recovery_suggestion: '检查页面HTML结构',
        })
      }
      else {
        this.logger.error('检测错误失败', { error: message })
      }
      return { success: false, error: message }
    }
  }

  /**
   * 验证日期格式
   *
   * @description 验证日期格式是否为 YYYY-MM-DD
   *
   * @param {string} dateStr - 日期字符串
   * @returns {boolean} 是否为有效日期
   */
  private isValidDate(dateStr: string): boolean {
    const match = dateStr.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/)
    if (!match)
      return false
    const [, day, month, year] = match
    const date = new Date(Number.parseInt(year), Number.parseInt(month) - 1, Number.parseInt(day))
    return (
      date.getFullYear() === Number.parseInt(year)
      && date.getMonth() === Number.parseInt(month) - 1
      && date.getDate() === Number.parseInt(day)
    )
  }

  /**
   * 解析日期字符串
   *
   * @description 解析日期字符串为 Date 对象
   *
   * @param {string} dateStr - 日期字符串
   * @returns {Date} 日期对象
   */
  private parseDateString(dateStr: string): Date {
    const [day, month, year] = dateStr.split('-').map(Number)
    return new Date(year, month - 1, day)
  }
}

/** 页面解析器实例 */
let pageParser: PageParser | null = null
/**
 * 获取页面解析器实例
 *
 * @description 获取全局唯一的页面解析器实例，采用单例模式确保实例状态一致性。
 *
 * @returns {PageParser} 页面解析器实例
 */
export function getPageParser(): PageParser {
  if (!pageParser)
    pageParser = new PageParser()
  return pageParser
}
